#include "all.h"
#include "types.i"
#include "extern.i"

namespace classifier_test
{
	
namespace c45r8
{
	short MaxAtt, MaxClass, MaxDiscrVal;
	DiscrValue *MaxAttVal;
	char* SpecialStatus;

	ItemNo MaxItem;
	Description *Item;

	String *ClassName, *AttName, **AttValName, FileName;
	short VERBOSITY = 0;
	short TRIALS;
	Boolean GAINRATIO = true, SUBSET = false, BATCH = true, UNSEENS = false, PROBTHRESH = false;
	ItemNo MINOBJS = 2, WINDOW = 0, INCREMENT = 0;
	float CF = 0.25;
	Tree *Pruned;

	Boolean AllKnown;

	extern ItemCount *Weight, **Freq, *ValFreq, *ClassFreq;
	extern float *Gain, *Info, *Bar, *UnknownRate;
	extern Boolean *Tested, MultiVal;
	extern float *SplitGain, *SplitInfo;
	extern ItemCount *Slice1, *Slice2;
	extern Set **Subset;
	extern short *Subsets;

	extern Set *PossibleValues;

	extern float *ClassSum;

	void serialize(serializer& Serializer, _tree_record& Val)
	{
		if (Serializer.is_loading())
			Val.ClassDist = (ItemCount*)calloc(MaxClass + 1, sizeof(ItemCount));
		Serializer
			("NodeType", Val.NodeType)
			("Leaf", Val.Leaf)
			("Items", Val.Items)
			("Errors", Val.Errors)
			("ClassDist", to_nonconst(make_subarray(Val.ClassDist, MaxClass + 1)));
		if (Val.NodeType != 0)
		{
			Serializer
				("Tested", Val.Tested)
				("Forks", Val.Forks);
			if (Val.NodeType == BrDiscr);
			else if (Val.NodeType == ThreshContin)
				Serializer
				("Cut", Val.Cut)
				("Lower", Val.Lower)
				("Upper", Val.Upper);
			else if (Val.NodeType == BrSubset)
				throw runtime_error("Don't support BrSubset");
			if (Serializer.is_loading())
				Val.Branch = (Tree*)calloc(Val.Forks + 1, sizeof(Tree));
			Serializer("Branch", to_nonconst(make_subarray(Val.Branch + 1, Val.Forks).convert<smart_ptr<_tree_record> >()));
		}
	}
}

namespace c45 = c45r8;

namespace
{
	struct backup
	{
	public:
		void set_domain(const record_domain& SetDomain);
		void apply();
	private:
		short MaxAtt, MaxClass, MaxDiscrVal;
		array<c45::DiscrValue> MaxAttVal;
		array<char> SpecialStatus;
	};

	void backup::set_domain(const record_domain& Domain)
	{
		MaxAtt = Domain.size() - 2;
		MaxClass = Domain.back().discr.count - 1;
		MaxDiscrVal = 2;
		MaxAttVal.resize(Domain.size() - 1);
		SpecialStatus.resize(Domain.size() - 1);
		for (int I = 0; I < Domain.size() - 1; I++)
			if (Domain[I].type == cont_attr)
			{
				SpecialStatus[I] = 0;
				MaxAttVal[I] = 0;
			}
			else
			{
				SpecialStatus[I] = c45::DISCRETE;
				MaxAttVal[I] = Domain[I].discr.count;
				if (MaxAttVal[I] > MaxDiscrVal) MaxDiscrVal = MaxAttVal[I];
			}
	}

	void backup::apply()
	{
		c45::MaxAtt = MaxAtt;
		c45::MaxClass = MaxClass;
		c45::MaxDiscrVal = MaxDiscrVal;
		c45::MaxAttVal = begin_ptr(MaxAttVal);
		c45::SpecialStatus = begin_ptr(SpecialStatus);
	}
}

c45r8_classifier::c45r8_classifier()
{
	Backup = new backup;
	Tree = 0;
}

c45r8_classifier::c45r8_classifier(const c45r8_classifier& Val)
	: classifier(Val)
{
	Backup = new backup;
	if (Val.Tree == 0)
		Tree = 0;
	else
	{
		((backup*)Backup)->set_domain(Domain);
		Tree = c45::CopyTree((c45::Tree)Val.Tree);
	}
}

c45r8_classifier::~c45r8_classifier()
{
	if (Tree != 0) c45::ReleaseTree((c45::Tree)Tree);
	delete (backup*)Backup;
}

smart_ptr<classifier> c45r8_classifier::clone() const
{
	return make_smart_ptr(new c45r8_classifier(*this));
}

void c45r8_classifier::serialize(serializer& Serializer)
{
	classifier::serialize(Serializer);
	((backup*)Backup)->set_domain(Domain);
	((backup*)Backup)->apply();
	Serializer("Tree", *(smart_ptr<c45::_tree_record>*)(void*)&Tree);
}

void c45r8_classifier::train(subarray<const record* const> Data)
{
	assert(Tree == 0);
	if (Data.empty()) return;
	for (int I = 0; I < Data.size(); I++) assert(Domain.valid(*Data[I]));

	((backup*)Backup)->set_domain(Domain);
	((backup*)Backup)->apply();
	c45::MaxItem = Data.size() - 1;
	c45::Item = new c45::Description[Data.size()];
	for (int I = 0; I <= c45::MaxItem; I++)
	{
		c45::Item[I] = new c45::AttValue[Domain.size()];
		for (int J = 0; J < Domain.size(); J++)
			if (Domain[J].type == cont_attr)
				c45::Item[I][J]._cont_val = (*Data[I])[J].cont;
			else if (J < Domain.size() - 1)
				c45::Item[I][J]._discr_val = (*Data[I])[J].discr + 1;
			else
				c45::Item[I][J]._discr_val = (*Data[I])[J].discr;
	}
	c45::AllKnown = true;
	c45::PossibleValues = 0;

	c45::InitialiseTreeData();
	c45::InitialiseWeights();
	Tree = c45::FormTree(0, c45::MaxItem);
	// c45::Prune((c45::Tree)Tree);

	free(c45::Weight);
	for (int I = 0; I <= c45::MaxDiscrVal; I++) free(c45::Freq[I]);
	free(c45::Freq);
	free(c45::ValFreq);
	free(c45::ClassFreq);
	free(c45::Gain);
	free(c45::Info);
	free(c45::Bar);
	free(c45::UnknownRate);
	free(c45::Tested);
	free(c45::SplitGain);
	free(c45::SplitInfo);
	free(c45::Slice1);
	free(c45::Slice2);
	for (int I = 0; I <= c45::MaxAtt; I++)
		if (c45::MaxAttVal[I] != 0)
		{
			for (int J = 0; J <= c45::MaxAttVal[I]; J++) free(c45::Subset[I][J]);
			free(c45::Subset[I]);
		}
	free(c45::Subset);
	free(c45::Subsets);
	if (c45::PossibleValues != 0)
		for (int I = 0; I <= c45::MaxAtt; I++) free(c45::PossibleValues[I]);
	free(c45::PossibleValues);
	c45::PossibleValues = 0;
	for (int I = 0; I <= c45::MaxItem; I++) delete[] c45::Item[I];
	delete[] c45::Item;
}

void c45r8_classifier::estimate(const record& Record, array<double>& Probs) const
{
	((backup*)Backup)->apply();
	c45::Description Des = new c45::AttValue[Domain.size()];
	for (int I = 0; I < Domain.size() - 1; I++)
	{
		assert(Domain[I].valid(Record[I]));
		if (Domain[I].type == cont_attr) Des[I]._cont_val = Record[I].cont;
		else Des[I]._discr_val = Record[I].discr + 1;
	}

	c45::ClassSum = 0;
	c45::Category(Des, (c45::Tree)Tree);
	Probs.resize(ClassCount);
	copy(c45::ClassSum, c45::ClassSum + ClassCount, begin_ptr(Probs));
	normalize_probabilities(Probs);
	free(c45::ClassSum);
	c45::ClassSum = 0;

	delete[] Des;
}

}
